#ifndef OBJECT
#define OBJECT
#include "common.h"

#include <vector>

struct Scene;

struct Object{
    Object(string type, Material mat):name(type), material(mat){
        light = 0;
    }
    Object():name("complex"), material(0.5, 0, 0.5, 0, RGB(1, 0 ,0)){
        light = 0;
    }

    string name; //物体类型，如"sphere"或"plane"
    Material material;
    bool light; //是否为光源
    virtual bool intersect (const Ray &a, Vec &nP , Vec &nN, Material &nMaterial) = 0; //碰撞点  反射面法向量 返回碰撞方式
};

struct Light{
    virtual double checkShade(Scene* scene,const Vec &P) = 0;
};

struct PointLight : Light{
    PointLight(const Vec& O, const RGB& color, const double& R):O(O), color(color), R(R){}
    Vec O;
    RGB color;
    double R;
    double checkShade(Scene* scene, const Vec &P);
    Vec getRandomLight();
};

struct AreaLight : Light{
    AreaLight(const Vec& O, const RGB& color, Vec dx, Vec dy):O(O), dx(dx), dy(dy), color(color){}
    Vec O, dx, dy;
    RGB color;
    double checkShade(Scene* scene, const Vec &P);
};

struct Sphere: public Object{

    Vec O; //球的球心和平面的法向量
    double radius;
    Sphere(Vec pos, double radius, string type, Material material)
        :Object(type, material), O(pos), radius(radius){}
    bool intersect(const Ray &ray, Vec &nP , Vec &nN, Material &nMaterial){ //碰撞点  反射面法向量 返回碰撞方式
        Vec v = ray.ori - this->O;
        double tmp = v.sqrlength() - sqr(this->radius);
        double dv = ray.dir * v;
        if (dv <= zero){
            double discr = dv * dv - tmp;
            if (discr < zero) return 0;
            double dis = - dv - sqrt(discr);  //distance
            nP = ray.getpoint(dis);   //position
            nN = nP - this->O;    //normal  法向量
            nN = nN.normalize();
            nMaterial = material;
            return 1;
        }
        return 0;
    }
};


struct Plane: public Object{
    Vec N; //平面的法向量
    Vec Wx;  //一向量
    Vec Wy;
    Vec T;// 平面到原点的垂足
    double radius;  //原点沿法线走radius个长度单位到达平面上的T
    Plane(Vec pN, double radius, string type, Material material)
        :Object(type, material), N(pN), radius(radius){

        Wx = (N + Vec(ran(),ran(),ran())).cross(N);
        Wx = Wx.normalize();
        Wy = Wx.cross(N).normalize();
        T = Ray(Vec(0,0,0), N).getpoint(radius);
    }
    bool intersect(const Ray &ray, Vec &nP , Vec &nN, Material &nMaterial){ //碰撞点  反射面法向量 返回碰撞方式
        double tmp = -(N * ray.ori);
        tmp = tmp + radius;
        if (fabs(tmp) < zero) return 0;
        double wm = ray.dir * N;
        if (fabs(wm) < zero) return 0;
        if (wm < zero) return 0;
        wm = tmp/wm;
        nP = ray.getpoint(wm);
        nN = N.negate();

        if (ray.dir * N > 0){
            nN = N.negate();
        }else {
            nN = N;
            cout << ray.dir * N << endl;
        }

        nMaterial = material;
        nMaterial.color = getColor(nP);
        return 1;
    }
    RGB getColor(Vec P){
        double x = (P - T) * Wx;
        if (x < 0) x = -x;
        double y = (P - T) * Wy;
        if (y < 0) y = -y;
        x = x / 10;
        y = y / 10;
        return material.getColor(x - floor(x), y - floor(y));
    }
};

struct Triangle : public Object{
    Triangle* next;
    int key;
    Vec p1, p2, p3;
    Vec t1, t2, t3;
    int d1, d2, d3;
    Vec N;
    double D;
    Matrix mm;
    Triangle(Vec a, Vec b, Vec c, string type = "triangle",
             Material material = Material( 0.9 , 0 , 0.1 , 0, RGB( 1 , 0 , 0 ))):
        Object(type, material), p1(a), p2(b), p3(c){
        Vec p12 = p2 - p1;
        Vec p13 = p3 - p1;
        N = p12.cross(p13);
        if (N.length() > zero)
            N = N.normalize();
        D =  - (N * p1);
        key = 0;
    }

    Triangle(int a, int b, int c, vector<Vec> &point, string type = "triangle",
             Material material = Material( 0.9 , 0 , 0.1 , 0, RGB( 1 , 0 , 0 )),
            Vec ta = Vec(), Vec tb = Vec(), Vec tc = Vec()):
        Object(type, material){
        d1 = a;
        d2 = b;
        d3 = c;
        p1 = point[a];
        p2 = point[b];
        p3 = point[c];
        t1 = ta;
        t2 = tb;
        t3 = tc;
        Vec p12 = p2 - p1;
        Vec p13 = p3 - p1;
        N = p12.cross(p13);
        if (N.length() > zero)
            N = N.normalize();
        D =  - (N * p1);
        key = 0;
    }


    void update(const vector<Vec> &point, int* father, int updateKey){
        d1 = getFather(d1, father);
        d2 = getFather(d2, father);
        d3 = getFather(d3, father);
        p1 = point[d1];
        p2 = point[d2];
        p3 = point[d3];
        key = updateKey;
        Vec p12 = p2 - p1;
        Vec p13 = p3 - p1;
        N = p12.cross(p13);
        if (N.length() > zero)
             N = N.normalize();
        D =  - (N * p1);
    }


    bool intersect(const Ray &a, Vec &nP, Vec &nN, Material &nMaterial){

        double tmp = N * a.dir;
        if (fabs(tmp) < zero) return 0; // parallel
        double t = -(N * a.ori + D) / tmp;
        if (t < zero){
            return 0;
        }
        Vec P = a.getpoint(t);

        double area1 = (p2 - p1).cross(P - p1).length();
        double area2 = (p3 - p2).cross(P - p2).length();
        double area3 = (p1 - p3).cross(P - p3).length();
        double areaD = (p2 - p3).cross(p1 - p3).length();
        if (fabs(area1 + area2 + area3 - areaD) > zero)
            return 0; // not in the triangle
        else {
            nP = P;
            nN = (N * (N * a.dir)).negate().normalize();
            nMaterial = material;
            nMaterial.color = getColor(P);
            return 1;
        }
    }

    RGB getColor(Vec P){
        if (material.bmp == 0){
            return material.color;
        }else {
            Matrix mm;
            mm.matrix[0][0] = p1.x; mm.matrix[0][1] = p2.x; mm.matrix[0][2] = p3.x;
            mm.matrix[1][0] = p1.y; mm.matrix[1][1] = p2.y; mm.matrix[1][2] = p3.y;
            mm.matrix[2][0] = p1.z; mm.matrix[2][1] = p2.z; mm.matrix[2][2] = p3.z;

            mm.matrix[0][3] = P.x;
            mm.matrix[1][3] = P.y;
            mm.matrix[2][3] = P.z;
            Vec ans = mm.solve();
            Vec pos = t1 * ans.x + t2 * ans.y + t3 * ans.z;
            return material.getColor(pos.x - floor(pos.x), pos.y - floor(pos.y));
        }
        //return material.getColor(x - floor(x), y - floor(y));
    }
};

#endif // OBJECT

